package rmi.analytics.server;

import java.rmi.AccessException;
import java.rmi.AlreadyBoundException;
import java.rmi.NoSuchObjectException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Scanner;

import org.apache.log4j.*;

import rmi.domain.RegistrySingleton;
import rmi.eventing.types.AuctionEvent;
import rmi.eventing.types.BidEvent;
import rmi.eventing.types.Event;
import rmi.eventing.types.StatisticEvent;
import rmi.eventing.types.UserEvent;
/*
 * Analytics Server creates Events and Statistics
 */



public class AnalyticsServer implements AnalyticsInterface {


	private Hashtable<Integer,RMISession> subscrmap =  new Hashtable<Integer,RMISession>();
	private Map<String, UserEvent> userperformance = new HashMap<String,UserEvent>();
	private int min_time = 999999999;
	private int max_time =0;
	private int avg_time =0;
	private int kom_time=0;
	private int usercount=0;
	private double maxprice =0;
	private int bitcount=0;
	private Calendar starttime=null;
	private int suc_aucts = 0;
	private int count_aucts =0;
	private long kom_auct_time=0;


	private  int subscriptionID=0;

	public static Logger log = Logger.getLogger(AnalyticsServer.class);

	AnalyticsServer(){
		super();
		starttime = Calendar.getInstance();
	}
	public static void main(String[] args) {
		String analyticsref="";
		//Parametervalitation
		if(args.length==1){
			analyticsref=args[0];

		}
		else{
			//Usage
			System.out.println("Usage: java AnalyticsServer <analytics.bindingName>");
			return;
		}

		//get registryreference
		Registry reg = RegistrySingleton.initialRegistry();
		//Instance of AnalyticsServer
		AnalyticsInterface anServ = new AnalyticsServer();
		try {
			//Export analyticsServer
			AnalyticsInterface stub = (AnalyticsInterface) UnicastRemoteObject.exportObject(anServ,0);
			//Bind analyticsServer to name in registry
			reg.bind(analyticsref, anServ);
		} catch (RemoteException e) {
			log.error("Error exporting AnalyticsServer"+e.getMessage());
		} catch (AlreadyBoundException e) {
			log.error("Object: AnalyticsServer is already bound: "+ e.getMessage());	
		}
		while(true){
			//Scanner for Exit
			Scanner sc = new Scanner(System.in);
			try{
			if(sc.nextLine().equals("!exit")){
				System.out.println("Shutting down AnalyticsServer");
				try {
					reg.unbind(analyticsref);
				} catch (AccessException e) {
					log.debug("No Access to Registry");

				} catch (RemoteException e) {
					log.debug("No Access to Registry");
				} catch (NotBoundException e) {
					log.debug("No Analyticsreference found in Registry");
				}
				try {
					UnicastRemoteObject.unexportObject(anServ, true);
				} catch (NoSuchObjectException e) {
					log.debug("AnalyticsServer was not exportet");
				}
				break;
			}
			else{
				System.out.println("Error: Unknown Command!");
			}
			}catch (NoSuchElementException e){
				log.debug(e.getMessage());
			}
		}

	}
	//Remotemethod to receive events
	public void processEvent (Event ev){
		log.debug("Enter Analytics processEvent Methode");
		//getTypes
		if (ev.getType().matches("(USER_.*)")){
			analysisUserEvent((UserEvent) ev);
		}
		else if (ev.getType().matches("(BID_.*)")){
			analysisBidEvent((BidEvent)ev);
		}
		else if (ev.getType().matches("(AUCTION_.*)")){
			analysisAuctEvent((AuctionEvent) ev);
		}
	}
	@Override
	//Remotemethod to subscribe
	public synchronized int subscribe(String filter, RemoteClientAnalyticsInterface rc) {
		log.debug("Client Subscribed");
		subscriptionID++;
		subscrmap.put(subscriptionID, new RMISession(rc,filter, subscriptionID));
		return subscriptionID;

	}
	//For Auctionevents
	public void analysisAuctEvent (AuctionEvent ev){
		if(ev.getType().equals("AUCTION_STARTED")){
			synchronized(subscrmap){
				for( Map.Entry<Integer, RMISession> entry : subscrmap.entrySet() ){
					try {
						entry.getValue().sendEvent(ev);
					} catch (RemoteException e) {
						clientNotAv(entry.getValue().getSubcsriptionID());
					}
				}
			}

		}
		else if (ev.getType().equals("AUCTION_ENDED")){
			count_aucts++;
			suc_aucts +=ev.getSucc();
			double rate =0;
			if (!(suc_aucts==0)){
				rate = ((double)suc_aucts/(double)count_aucts);
			}
			StatisticEvent sucratev = new StatisticEvent(rate);
			sucratev.setType(StatisticEvent.Type.AUCTION_SUCCESS_RATIO);

			kom_auct_time+=((ev.getTimestamp()/1000)-(ev.getStartTime()/1000));
			StatisticEvent avgauctev = new StatisticEvent((kom_auct_time/(double)count_aucts));
			avgauctev.setType(StatisticEvent.Type.AUCTION_TIME_AVG);


			synchronized(subscrmap){
				for( Map.Entry<Integer, RMISession> entry : subscrmap.entrySet() ){
					try {
						entry.getValue().sendEvent(ev);
						entry.getValue().sendEvent(sucratev);
						entry.getValue().sendEvent(avgauctev);
					} catch (RemoteException e) {
						clientNotAv(entry.getValue().getSubcsriptionID());
					}

				}
			}
		}


	}
	//For BidEvents
	public void analysisBidEvent (BidEvent ev){
		if (ev.getType().equals("BID_PLACED")){


			bitcount++;
			Calendar endtime=Calendar.getInstance();

			double timedif = ((((double)endtime.getTimeInMillis())/60000))-(((double)starttime.getTimeInMillis())/60000);
						
			StatisticEvent bidpminev = new StatisticEvent(((double)bitcount/(double)timedif));
			bidpminev.setType(StatisticEvent.Type.BID_COUNTER_PER_MINUTE);


			if(ev.getPrice()>maxprice){
				maxprice = ev.getPrice();
				StatisticEvent maxprev = new StatisticEvent(ev.getPrice());
				maxprev.setType(StatisticEvent.Type.BID_PRICE_MAX);
				synchronized(subscrmap){
					for( Map.Entry<Integer, RMISession> entry : subscrmap.entrySet() ){
						try {
							entry.getValue().sendEvent(ev);
							entry.getValue().sendEvent(maxprev);
							entry.getValue().sendEvent(bidpminev);
						} catch (RemoteException e) {
							clientNotAv(entry.getValue().getSubcsriptionID());
						}

					}
				}
			}
			else{
				synchronized(subscrmap){
					for( Map.Entry<Integer, RMISession> entry : subscrmap.entrySet() ){
						try {
							entry.getValue().sendEvent(ev);
							entry.getValue().sendEvent(bidpminev);
						} catch (RemoteException e) {
							clientNotAv(entry.getValue().getSubcsriptionID());
						}

					}
				}
			}
		}
		else {
			synchronized(subscrmap){
				for( Map.Entry<Integer, RMISession> entry : subscrmap.entrySet() ){
					try {
						entry.getValue().sendEvent(ev);
					} catch (RemoteException e) {
						clientNotAv(entry.getValue().getSubcsriptionID());
					}
				}
			}
		}

	}
	//For Userevents
	public void analysisUserEvent(UserEvent ev){
		if(ev.getType().equals("USER_LOGIN")){
			synchronized (userperformance){
				userperformance.put(ev.getUsername(), ev);
			}
			synchronized(subscrmap){
				for( Map.Entry<Integer, RMISession> entry : subscrmap.entrySet() ){
					try {
						entry.getValue().sendEvent(ev);
					} catch (RemoteException e) {
						clientNotAv(entry.getValue().getSubcsriptionID());
					}
				}
			}
		}
		else{
			UserEvent storedEv=null;
			synchronized (userperformance){
				 storedEv  = userperformance.get(ev.getUsername());
			}
			if (!(storedEv==null)){
				double time = ev.getTimestamp()-storedEv.getTimestamp();

				if (time<min_time){
					min_time =(int) time;
				}
				if (time>max_time){
					max_time =(int) time;
				}
				kom_time +=(int)time;
				usercount++;
				avg_time = (kom_time/usercount);
				userperformance.remove(ev.getUsername());

				StatisticEvent minev = new StatisticEvent((min_time/1000));
				minev.setType(StatisticEvent.Type.USER_SESSIONTIME_MIN);
				StatisticEvent avgev = new StatisticEvent((avg_time/1000));
				avgev.setType(StatisticEvent.Type.USER_SESSIONTIME_AVG);
				StatisticEvent maxev = new StatisticEvent((max_time/1000));
				maxev.setType(StatisticEvent.Type.USER_SESSIONTIME_MAX);

				synchronized(subscrmap){
					for( Map.Entry<Integer, RMISession> entry : subscrmap.entrySet() ){
						try {
							entry.getValue().sendEvent(ev);
							entry.getValue().sendEvent(minev);
							entry.getValue().sendEvent(avgev);
							entry.getValue().sendEvent(maxev);
						} catch (RemoteException e) {
							clientNotAv(entry.getValue().getSubcsriptionID());
						}

					}
				}
			}
			else{
				synchronized(subscrmap){
					for( Map.Entry<Integer, RMISession> entry : subscrmap.entrySet() ){
						try {
							entry.getValue().sendEvent(ev);
						} catch (RemoteException e) {
							clientNotAv(entry.getValue().getSubcsriptionID());
						}

					}
				}
			}



		}
	}
	//Unsubscribe
	public synchronized String unsubscribe(int id) {
		if (subscrmap.containsKey(id)){
			subscrmap.remove(id);
			return "subscription "+id+" terminated";
		}
		return "No subscription with ID "+id+" was found. Unsubscription was not successfull!";
	}
	//called if subscipted MC ist not available
	public void clientNotAv(int subid){
		System.out.println(("ERROR: ManagementClient for Subscription ID "+ subid+" is not available anymore"));
		log.debug("Delete Subsription with ID "+subid+".");
		log.debug(unsubscribe(subid));	
	}


}
